multi property
是一個容易被忽略的功能,可以視為以EdgeDBSet
為容器來收集各種primitive
。
考慮schema如下:
type User {
multi sports: str;
}
User object
中有一個multi sports property
,為str
型別,用來記錄其喜愛的運動。
接著我們新增一個User object
,並將{"baseball", "swimming"}
這個EdgeDBSet
指定給multi sports property
。
insert User {
sports:= {"baseball", "swimming"}
};
假設我們想將badminton加入multi sports property
中,可以這麼寫:
update User
set {
sports+= {"badminton"}
};
上面這個query利用+=
來新增badminton,是一個快速將元素加入EdgeDBSet
的寫法,可以想成in place
地加入元素。
如果不習慣這樣的寫法,也可以寫成:
update User
set {
sports:= .sports union {"badminton"}
};
留意這邊使用的是:=
。:=
代表將.sports union {"badminton"}
這個set operation
的結果指定給multi sports property
。
假設我們想將badminton自multi sports property
移除,可以這麼寫:
update User
set {
sports-= {"badminton"}
};
或是寫成:
update User
set {
sports:= .sports except {"badminton"}
};
留意這邊使用的是:=
。:=
代表將.sports except {"badminton"}
這個set operation
的結果指定給multi sports property
。
array是有序的collection type
,與EdgeDBSet
一樣,其內所含的object
須為同一型別,且可以被索引(索引值由0開始)。例如:
select [1, 2, 3][0];
{1}
請注意,此query返回結果依然是EdgeDBSet
。
除了利用索引取得單個元素外,也可以使用切片,例如:
select [1, 2, 3][1:];
{[2, 3]}
切片邏輯與Python相似。舉例來說,當select array[from:to]
:
考慮schema如下:
type User {
sports: array<str>;
}
接著我們新增一個User object
,並將["baseball", "swimming"]
這個array
指定給sports property
。
insert User {
sports:= ["baseball", "swimming"]
};
假設我們想將badminton加入sports property
中,可以這麼寫:
update User
set {
sports:= .sports ++ ["badminton"]
};
請留意這邊我們需要使用:=
及++
。EdgeDB在設計上,將array
視為immutable
,所以我們其實無法修改array
,只能夠於每次需要更新array
時,重新建立一個,然候指定給原先的property
。
假設我們想將badminton自sports property
移除,可以這麼寫:
update User
set {
sports:= (
with idx:= find(.sports, "badminton")
select .sports[:idx] ++ .sports[idx+1:]
)
};
這邊我們利用find先找出badminton所在的索引值,接著再手動重新建立一個排除badminton的array
,指定給sports property
。
由於找不到元素時,find
會回傳-1,上面的query將因此產生錯誤的答案。此時,我們可以透過if-else
來幫忙,當想要移除的元素不在array
中時,我們只是將原先的array
再一次指定回sports property
。
update User
set {
sports:= (
with idx:= find(.sports, "badminton")
select .sports[:idx] ++ .sports[idx+1:] if idx != -1 else .sports
)
};
如果覺得上面邏輯有點亂的話,可以試著在with
區塊中透過contains
預先建立一個判斷邏輯,來檢視array
中是否有該元素:
update User
set {
sports:= (
with is_in:= contains(.sports, "badminton"),
idx:= find(.sports, "swimming")
select .sports[:idx] ++ .sports[idx+1:] if is_in else .sports
)
};
multi property
vs array
至於何時該選擇multi property
而何時又該選擇array
呢?根據David MacLeody在Easy EdgeDB第八章所建議的:
multi property
;反之則使用array
。constraint
或針對其使用index on
時,使用multi property
。array
。由於使用array
需要於每次update
重新生成一個array
,操作起來比較麻煩。所以我個人會傾向將array
使用在元素順序重要,且更新時只需不斷使用++
將新元素添加到array
的情況。
EdgeDBSet
與array
的互相轉換array_unpack()
array_unpack()可以將array
轉變為EdgeDBSet
:
select array_unpack([2, 3, 5]);
{3, 2, 5}
這邊需留意由array
轉變為EdgeDBSet
時,順序有可能會打亂(記得EdgeDBSet
為無序的嗎?)。
array_agg()
array_agg()可以將EdgeDBSet
轉變為array
:
select array_agg({2, 3, 5});
{[2, 3, 5]}
這邊需留意由EdgeDBSet
轉變為array
時,有兩種情況可以將順序將會保留在array
中:
EdgeDBSet
為顯性寫出來的型態,如{2, 3, 5}
。order by
時。舉例來說,考慮schema如下:
type User {
required name: str;
}
接著執行下面query,生成三個User object
:
insert User {name:= "Jeff"};
insert User {name:= "Tom"};
insert User {name:="Cathy"};
此時下面query因為使用了order by
,所以array
內元素將會依照字母順序排好:
select array_agg(User.name order by User.name);
{['Cathy', 'Jeff', 'Tom']}